package se.webbzon.boltzmann.game;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

import se.webbzon.boltzmann.battle.environment.BattleEnvironment;
import se.webbzon.boltzmann.battle.environment.BattleInflatorGroup;
import se.webbzon.boltzmann.battle.factory.property.DefaultBPFGroup;
import se.webbzon.boltzmann.environment.VariableMap;
import se.webbzon.boltzmann.game.environment.GameEnvironment;
import se.webbzon.boltzmann.game.environment.GameInflatorGroup;
import se.webbzon.boltzmann.game.environment.RegionObjectImpl;
import se.webbzon.boltzmann.inventory.Inventory;
import se.webbzon.boltzmann.item.AbstractItem;
import se.webbzon.boltzmann.item.ItemGroup;
import se.webbzon.boltzmann.player.CharacterEnum;
import se.webbzon.boltzmann.player.Player;
import se.webbzon.boltzmann.player.PlayerModel;
import se.webbzon.boltzmann.world.RegionEnum;

public class GameReader {
	
	// The bass version
	private static final int version = 1;
	
	/*============================================================
	Public Methods
	============================================================*/
	
	public GameEnvironment read(Game game, GameConfiguration conf, InputStream is) throws IOException {
		DataInputStream dis = new DataInputStream(is);
		byte[] recognizer = new byte[4];
		dis.readFully(recognizer);
		if (!Arrays.equals(recognizer, new byte[] {'B','A','S','S'}))
			throw new IOException("The provided format is not supported!");
		
		int bassVersion = dis.readInt();
		if (version > bassVersion)
			throw new IOException("The provided BASS-version is not supported!");
		
		// Create game environment
		GameEnvironment env;
		try {
			BattleEnvironment benv = new BattleEnvironment(new BattleInflatorGroup());
			env = new GameEnvironment(game, benv, new GameInflatorGroup(new DefaultBPFGroup()));
		} catch (Exception e) {
			throw new IOException(e);
		}
		
		// Read the player properties from the stream
		int nrOfPlayers = dis.readByte();
		PlayerModel[] players = new PlayerModel[nrOfPlayers];
		for (int i = 0; i < nrOfPlayers; i++) {
			final PlayerModel player;
			if (i >= nrOfPlayers) {
				player = new PlayerModel();
			} else {
				Player p = new Player(conf.getPlayer(i));
				env.addPlayer(p);
				player = p;
			}
			
			int size = dis.readInt();
			
			// Read player properties
			player.setCharacter(CharacterEnum.values()[dis.readInt()].getCharacter());
			player.getHealth().setMaxHealth(dis.readInt());
			player.getHealth().setHealth(dis.readInt());
			player.getEnergy().setMaxEnergy(dis.readInt());
			player.getEnergy().setEnergy(dis.readInt());
			player.getCoins().setMaxCoins(dis.readInt());
			player.getCoins().setCoins(dis.readInt());
			player.getLevel().setLevel(dis.readInt());
			player.getLevel().setNotes(dis.readInt());
			readInventory(player.getInventory(),dis);
			
			// Skip overflowing bytes
			int invSize = player.getInventory().getMaxItems();
			int estimatedSize = 40 + invSize * 4;
			if (size > estimatedSize)
				dis.skip(size - estimatedSize);
			
			players[i] = player;
		}
		
		// Read the location of the players
		{
			int locationSize = dis.readInt();
			if (locationSize > 0) {
				byte[] nameData = new byte[dis.readShort()];
				dis.readFully(nameData);
				String worldAreaName = new String(nameData,"UTF-8");
				byte portalId = dis.readByte();
				
				// Skip overflowing bytes
				int estimatedSize = 3 + nameData.length;
				if (estimatedSize > locationSize)
					dis.skip(locationSize - estimatedSize);
				
				// Set the location for all the players
				RegionEnum region = RegionEnum.fromWorldAreaName(worldAreaName);
				for (PlayerModel player : players) {
					player.getLocation().setRegion(region);
					player.getLocation().setWorldAreaName(worldAreaName);
					player.getLocation().setPortalId(portalId);
				}
			}
		}
		
		// Read the game persistences
		{
			int perSize = dis.readInt();
			int peristences = dis.readInt();
			for (int i = 0; i < peristences; i++)
				env.addGamePersistence(new RegionObjectImpl(dis.readLong()));
			// Skip overflowing bytes
			int estimatedSize = 8 * peristences;
			if (estimatedSize > perSize)
				dis.skip(perSize - estimatedSize);
		}
		
		// Read the variable map
		{
			// Skip some bytes
			dis.readInt(); // int varSize = 
			
			VariableMap variables = env.getVariables();
			
			// The double values
			int doubles = dis.readInt();
			for (int i = 0; i < doubles; i++) {
				byte[] nameData = new byte[dis.readShort()];
				dis.readFully(nameData);
				String name = new String(nameData,"UTF-8");
				double value = dis.readDouble();
				variables.assign(name, value);
			}
			
			// The string values
			int strings = dis.readInt();
			for (int i = 0; i < strings; i++) {
				byte[] nameData = new byte[dis.readShort()];
				dis.readFully(nameData);
				String name = new String(nameData,"UTF-8");
				byte[] valueData = new byte[dis.readShort()];
				dis.readFully(valueData);
				String value = new String(valueData,"UTF-8");
				variables.assign(name, value);
			}
		}
		
		return env;
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Reads an inventory from the given data input stream. If the inventory
	 * could not be read then an IOException is thrown. **/
	private void readInventory(Inventory inv, DataInputStream dis) throws IOException {
		int slots = dis.readInt();
		
		// Find the proper size of the inventory
		Inventory.Size[] sizes = Inventory.Size.values();
		for (Inventory.Size size : sizes) {
			if (Inventory.columnsOf(size) * Inventory.rowsOf(size) == slots) {
				inv.setInventorySize(size);
				
				// Read all the item to the inventory
				int columns = Inventory.columnsOf(size);
				for (int i = 0; i < slots; i++) {
					int row = i / columns;
					int column = i % columns;
					inv.removeItem(row, column);
					int itemId = dis.readInt();
					if (itemId != -1) {
						AbstractItem item = ItemGroup.instance().getItem(itemId);
						if (item != null)
							inv.addItem(item, row, column);
					}
				}
				
			}
		}
	}

}
